/*
FormValidation.js v. 1.0.4: A JavaScript form validation library.
Copyright (c) 2001, 2002, 2003, 2004, Paul Strack

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/



/***********************************************************
*            Section: Key Object Initializers              *
*                                                          *
* The section has the definitions of the constructors and  *
* initializaters for the key objects created by this       *
* library:                                                 *
*                                                          *
* <ul>                                                     *
*   <li><code>Validator</code> objects: A separate         *
*       <code>Validator</code> object must be created for  *
*       each field with validations.</li>                  *
*   <li><code>formdata</code> object: This object has      *
*       utilities for manipulation form data.</li>         *
*   <li><code>formitems</code> array: This object has      *
*       utilities for manipulation form data for fields    *
*       ares in groups of elements all with the same       *
*       name.</li>                                         *
* </ul>                                                    *
***********************************************************/

/**
 * A constructor for <code>Validator</code> objects. It
 * should be assigned to a <code>validations</code>
 * property whose name matches the field being validated.
 * Any validation properties for that field should be
 * assigned to this object:
 *
 * <pre>
 * // Example validations for an amount field:
 * validations.amount = new <b>Validator("Item Amount")</b>;
 * validations.amount.datatype = "Integer";
 * validations.amount.compute = computeTotals;</pre>
 *
 * A <code>Validator</code> object is a prerequisite
 * for setting validations for a field. The properties
 * and methods assigned to <code>Validator</code> objects
 * are copied to their corresponding field elements. In
 * addition, each field is assigned a <code>label</code>
 * property matching the parameter passed to its
 * <code>Validator</code> constructor.
 *
 * <pre>
 * // The validations copied to the amount field:
 * document.<i>form</i>.amount.label = <b>"Item Amount"</b>;
 * document.<i>form</i>.amount.datatype = "Integer";
 * document.<i>form</i>.amount.compute = computeTotals;</pre>
 *
 * @param label A human readable label for the field.
 */
function Validator(label) {
  this.label = label;
}

/**
 * This function initializes the validation methods 
 * for the page's form. This function should be
 * called in the <code>onload</code> event handler
 * of the <code>&lt;BODY></code> tag:
 *
 * <pre>
 * &lt;body onload="initFormValidation()"></pre>
 *
 * More specifically, this function:
 *
 * <ul>
 *   <li>Sets the <code>getIterator()</code> method
 *       of the form.</li>
 *   <li>Sets the <code>validate()</code> method.</li>
 *   <li>Sets the <code>onsubmit</code> event handler
 *       to validate the form.</li>
 *   <li>Calls <code>initFieldValidation()</code> for
 *       each field in the form.</li>
 *   <li>Calls <code>initFormData()</code> to
 *       initialize the <code>formdata</code> object
 *       and the <code>formitems</code> array.</li>
 *   <li>Calls the <code>format()</code> and
 *       <code>compute()</code> methods of all fields
 *       that have them.</li>
 * </ul>
 *
 * Of these operations, the <code>initFormData()</code>
 * is the most fundamental.
 */
function initFormValidation() {
  for (var i=0; i<document.forms.length; i++) {
    var form = document.forms[i];
    form.getIterator = function() {return new FormIterator(this);}
    form.validate = form_validate;
    form.onsubmit = function() { return this.validate();}
    for (var j=0; j<form.length; j++) {
      initFieldValidation(form.elements[j]);
    }
    initFormData(form);
  }

  for (var i=0; i<document.forms.length; i++) {
    for (var j=0; j<form.length; j++) {
      var field = form.elements[j];
      if (field.format) field.format();
      if (field.compute) field.compute();
    }
  }
}

/**
 * Defines form field manipulation methods and assigns
 * them to the global <code>formdata</code> object.
 * There are multiple sets of methods, one set for each
 * field in the form:
 *
 * <ul>
 *   <li><code>get<i>Field</i>()</code>: Returns the
 *       field's value, parsed if appropriate.</li>
 *   <li><code>set<i>Field</i>(value)</code>: Sets the
 *       field's value, formatting it if appropriate.</li>
 *   <li><code>compute<i>Field</i>()</code>: Calls the
 *       field's <code>compute</code> method for
 *       calculations and mult-field validations.</li>
 *   <li><code>get<i>Field</i>Text()</code>: Returns the
 *       field's text (select lists only).</li>
 * </ul>
 *
 * In each case, <code><i>Field</i></code> is replaced
 * by the field's capitalized name. The last two methods
 * are only present if the field has a matching method
 * (<code>compute</code> and <code>getText</code>). All
 * field manipulation methods can be called through the 
 * global <code>formdata</code> object. For example:
 *
 * <pre>
 * formdata.set<i>Field</i>(x);</pre>
 *
 * This method is roughly equivalent to:
 *
 * <pre>
 * document.<i>form.field</i>.setValue(x);</pre>
 *
 * For text fields, it is roughly equivalent to:
 *
 * <pre>
 * document.<i>form.field</i>.value = x;</pre>
 *
 * If the form includes repeated groups of non-radio
 * button elements, this method also initializes a
 * <code>formitems</code> array. Each item in this
 * array is like a <code>formdata</code> object for
 * the element group. For example, to set the value
 * for the <i>Field</i> with index <code>i</code> in
 * the group:
 *
 * <pre>
 * formitems[i].set<i>Field</i>(value);</pre>
 *
 * @param form   The form whose fields are added to the
 *               <code>formdata</code> object.
 */
function initFormData(form) {
  for (var i=0; i<form.length; i++) {
    var element = form.elements[i];
    var name = element.name;

    if ((name != null) && (name != "")) {
      name = name.charAt(0).toUpperCase() + name.substring(1);
      var target = "document.forms[0].elements[" + i + "]";
      var baseObject = formdata;
      var index = nonRadioIndex(element);

      if (index >= 0) {
        if (!formitems[index]) {
          formitems[index] = new Object();
        }
        baseObject = formitems[index];
      }

      // Define getFIELD() method:
      var getter = "return " + target + ".getValue();";
      if (element.parse) {
        getter = "return " + target + ".parse();";
      }
      baseObject["get" + name] = new Function(getter);

      // Define setFIELD() method:
      var setter = target + ".setValue(value);";
      if (element.format) {
        var setter = target + ".setValue(validations['" + 
                     element.name + "'].format(value));";
      }
      baseObject["set" + name] = new Function("value", setter);

      // Define getFIELDText() method:
      if (element.getText) {
        var getText = "return " + target + ".getText();";
        baseObject["get" + name + "Text"] = new Function(getText);
      }

      // Define computeFIELD() method:
      if (element.compute) {
        var compute = "return " + target + ".compute();";
        baseObject["compute" + name] = new Function(compute);
      }
    }
  }
}



/***********************************************************
*               Section: Formatting Functions              *
*                                                          *
* These functions format field values. Each field is       *
* assigned the formatting function appropriate to its data *
* type as a method. Each method reformats the contents of  *
* the field value in a way appropriate to the data type.   *
* Each method returns the formatted value or an error      *
* string. The error string is always in the form           *
* <code>"ERROR:<i>message</i>"</code>.                     *
***********************************************************/

/**
 * Formats a value to a positive integer string. It is
 * assigned to fields with data type "Integer".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The integer string or an error string if 
 *         the formatting failed.
 */
function formatInteger(value) {
  value = "" + value; // Force conversion to string
  if (value == "") return "";
  value = parseInteger(value);
  if (isNaN(value)) {
    return "ERROR:" + NUMBER_ERROR.replace("{0}", this.label);
  } else {
    return "" + value; // Force conversion to string
  }
}

/**
 * Formats a value to a signed integer string. It is
 * assigned to fields with data type "SignedInteger".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The integer string or an error string if 
 *         the formatting failed.
 */
function formatSignedInteger(value) {
  value = "" + value; // Force conversion to string
  if (value == "") return "";
  value = parseSignedInteger(value);
  if (isNaN(value)) {
    return "ERROR:" + NUMBER_ERROR.replace("{0}", this.label);
  } else {
    return "" + value; // Force conversion to string
  }
}

/**
 * Formats a value to a positive decimal string. It is
 * assigned to fields with data type "Decimal". Decimal fields
 * may have an optional <code>scale</code> property, indicating
 * the number of places after the decimal point. For example,
 * a decimal field with a precision of 3 will always be
 * formatted <code>#.###</code>. Missing values are filled
 * with 0's. Excess values are rounded.
 *
 * <pre>
 * validations.<i>field</i>.scale = #;</pre>
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The decimal string or an error string if 
 *         the formatting failed.
 */
function formatDecimal(value) {
  value = "" + value; // Force conversion to string
  if (value == "") return "";
  value = parseDecimal(value);

  if (isNaN(value)) {
    return "ERROR:" + NUMBER_ERROR.replace("{0}", this.label);
  } else if (this.scale != null) {
    // Add scaling value for rounding:
    value += parseFloat("1E-" + this.scale)/2;
  }

  value += ""; // Force conversion to string
  // In Netscape, decimals under 1.0 have no leading zero:
  if (value.charAt(0) == ".") {
    value += "0";
  }

  // Scale as appropriate:
  if ((this.scale != null) && (value != "")) {
    var decimalPlace = value.indexOf(".");
    var decimalPart = "";
    if (decimalPlace < 0) {
      decimalPart = ".";
    } else {
      var decimalEnd = decimalPlace + this.scale + 1;
      decimalPart = value.substring(decimalPlace, decimalEnd);
      value = value.substring(0, decimalPlace);
    }
    while (decimalPart.length < (this.scale + 1)) {
      decimalPart += "0";
    }
    value += decimalPart;
  }

  return value;
}

/**
 * Formats a value to a signed decimal string. It is
 * assigned to fields with data type "SignedDecimal".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The decimal string or an error string if 
 *         the formatting failed.
 */
function formatSignedDecimal(value) {
  value = "" + value; // Force conversion to string
  var sign = "";

  // Strip leading spaces:
  while (value.charAt(0) == " ") value = value.substring(1);
  if (value.charAt(0) == "-") sign = "-";

  var result = formatDecimal(value);
  if (result.indexOf("ERROR:") == 0) {
    return "ERROR:" + NUMBER_ERROR.replace("{0}", this.label);
  } else {
    return sign + result;
  }
}

/**
 * Formats a value to a positive currency string
 * (<code>$##,###</code>). It is assigned to fields with
 * data type "Currency".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The currency string or an error string if 
 *         the formatting failed.
 */
function formatCurrency(value) {
  value = "" + value; // Force conversion to string
  if (value == "") return "";
  value = parseDecimal(value);

  if (isNaN(value)) {
    return "ERROR:" + NUMBER_ERROR.replace("{0}", this.label);
  } else {
    // Add scaling value for rounding:
    value += 0.005;
  }

  value += ""; // Force conversion to string
  // In Netscape, decimals under 1.0 have no leading zero:
  if (value.charAt(0) == ".") {
    value += "0";
  }

  var decimalPlace = value.indexOf(".");
  var decimalPart = "";
  if (decimalPlace < 0) {
    decimalPart = ".";
  } else {
    var decimalEnd = decimalPlace + 3;
    decimalPart = value.substring(decimalPlace, decimalEnd);
    value = value.substring(0, decimalPlace);
  }
  while (decimalPart.length < 3) {
    decimalPart += "0";
  }

  var newValue = "";
  for (var i=value.length; i>3; i-=3) {
    newValue = "," + value.substring(i-3, i) + newValue;
  }
  newValue = "$" + value.substring(0,i) + newValue + decimalPart;

  return newValue;
}

/**
 * Formats a value to a signed currency string. It is
 * assigned to fields with data type "SignedCurrency".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The currency string or an error string if 
 *         the formatting failed.
 */
function formatSignedCurrency(value) {
  value = "" + value; // Force conversion to string
  value += ""; // Force conversion to string
  var sign = "";

  // Strip leading spaces:
  while (value.charAt(0) == " ") value = value.substring(1);
  if (value.charAt(0) == "-") sign = "-";

  var result = formatCurrency(value);
  if (result.indexOf("ERROR:") == 0) {
    return "ERROR:" + NUMBER_ERROR.replace("{0}", this.label);
  } else {
    return sign + result;
  }
}

/**
 * Formats a value to a phone number string with area code
 * (<code>###-###-####</code>). It is assigned to fields
 * with data type "Phone".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The phone string or an error string if 
 *         the formatting failed.
 */
function formatPhone(value) {
  if (value == "") return "";

  value = stripNonNumeric(value);
  if (value.length > 3) {
    var oldValue = value;
    value = value.substring(0,3) + "-" + value.substring(3,6);
    if (oldValue.length > 6) {
      value += "-" + oldValue.substring(6,10);
    }
  }

  if (value.length != 12) {
    return "ERROR:" + PHONE_ERROR.replace("{0}", this.label);
  } else {
    return value;
  }
}

/**
 * Formats a value to a SSN number string
 * (<code>###-##-####</code>). It is assigned to fields
 * with data type "SSN".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The SSN string or an error string if 
 *         the formatting failed.
 */
function formatSSN(value) {
  if (value == "") return "";

  value = stripNonNumeric(value);
  if (value.length > 3) {
    var oldValue = value;
    value = value.substring(0,3) + "-" + value.substring(3,5);
    if (oldValue.length > 5) {
      value += "-" + oldValue.substring(5,9);
    }
  }

  if (value.length != 11) {
    return "ERROR:" + SSN_ERROR.replace("{0}", this.label);
  } else {
    return value;
  }
}

/**
 * Formats a value to a postal code string (<code>#####</code>
 * or <code>#####-####</code>). It is assigned to fields with
 * data type "Postal".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The postal string or an error string if 
 *         the formatting failed.
 */
function formatPostal(value) {
  if (value == "") return "";

  value = stripNonNumeric(value);
  if (value.length > 5) {
    value = value.substring(0,5) + "-" + value.substring(5,9);
  }

  if ((value.length != 5) && (value.length != 10)) {
    return "ERROR:" + POSTAL_ERROR.replace("{0}", this.label);
  } else {
    return value;
  }
}

/**
 * Formats a value to a valid email string. It is
 * assigned to fields with data type "Email".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The email string or an error string if 
 *         the formatting failed.
 */
function formatEmail(value) {
  if (value == "") return "";

  value += ""; // Force conversion to string
  var indexOfAtSign = value.indexOf("@");
  var indexOfPeriod = value.lastIndexOf('.');

  if (value.indexOf(" ") >= 0) {
    // Ensure there are no spaces:
    return "ERROR:" + NO_SPACE_ERROR.replace("{0}", this.label);
  } else if (indexOfAtSign < 1) {
    // Ensure there is one "@", not at the beginning:
    return "ERROR:" + EMAIL_ERROR.replace("{0}", this.label);
  } else if (value.indexOf("@", indexOfAtSign+1) >= 0) {
    // Ensure there is not a second "@":
    return "ERROR:" + EMAIL_ERROR.replace("{0}", this.label);
  } else if ((indexOfPeriod-indexOfAtSign) < 2) {
    // Ensure there is a character between "." and "@":
    return "ERROR:" + EMAIL_ERROR.replace("{0}", this.label);
  } else if ((value.length - indexOfPeriod) < 3) {
    // Ensure there are 2 characters after the ".":
    return "ERROR:" + EMAIL_ERROR.replace("{0}", this.label);
  } else {
    return value;
  }
}

/**
 * Formats a value to a valid date string
 * (<code>mm/dd/yyyy</code>). It is assigned to fields
 * with data type "Date".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The date string or an error string if 
 *         the formatting failed.
 */
function formatDate(value) {
  if (value == "") return "";

  var date = parseDate(value);
  if (isNaN(date)) {
    return "ERROR:" + DATE_ERROR.replace("{0}", this.label);
  } else {
    var month = date.getMonth()+1;
    var day = date.getDate();
    var year = date.getFullYear();
    return month + "/" + day + "/" + year;
  }
}



/***********************************************************
*                Section: Parsing Functions                *
*                                                          *
* These methods parse string values into various data      *
* types. They are used primarily to parse the values of    *
* form fields. Each field is assigned the parsing function *
* appropriate to its data type. The parsing methods return *
* a value of <code>NaN</code> if the value passed to the   *
* method does not parse to the desired value.              *
***********************************************************/

/**
 * This method parses a string into an integer value.
 * It is more forgiving than Javascript's standard
 * <code>parseInt()</code> method because it ignores
 * <i>all</i> non-decimal characters in the string.
 * This method returns a positive integer. To allow
 * negative integers, use <code>parseSignedInteger()</code>
 * instead.
 *
 * @param value The value to be parsed.
 * @return The positive integer value of the string, or
 *         <code>NaN</code> if the string contains no
 *         numbers.
 */
function parseInteger(value) {
  return parseInt(stripNonDecimal(value));
}

/**
 * As <code>parseInteger()</code>, but allows negative
 * integers as well. This is not the default because for
 * most applications, allowing negative values can lead
 * to unexpected results and possibly security violations.
 *
 * @param value The value to be parsed.
 * @return The signed integer value of the string, or
 *         <code>NaN</code> if the string contains no
 *         numbers.
 */
function parseSignedInteger(value) {
  value += ""; // Force conversion to string.
  while (value.charAt(0) == " ") {
    value = value.substring(1);
  }
  var sign = 1;
  if (value.charAt(0) == "-") {
    sign = -1;
  }
  return sign * parseInteger(value);
}

/**
 * This method parses a string into a decimal value.
 * It is more forgiving than Javascript's standard
 * <code>parseFloat()</code> method because it ignores
 * <i>all</i> non-decimal characters in the string.
 * This method returns a positive number. To allow
 * negative numbers, use <code>parseSignedDecimal()</code>
 * instead.
 *
 * @param value The value to be parsed.
 * @return The positive decimal value of the string, or
 *         <code>NaN</code> if the string contains no
 *         numbers.
 */
function parseDecimal(value) {
  return parseFloat(stripNonDecimal(value));
}

/**
 * As <code>parseDecimal()</code>, but allows negative
 * values as well. This is not the default because for
 * most applications, allowing negative values can lead
 * to unexpected results and possibly security violations.
 *
 * @param value The value to be parsed.
 * @return The signed decimal value of the string, or
 *         <code>NaN</code> if the string contains no
 *         numbers.
 */
function parseSignedDecimal(value) {
  value += ""; // Force conversion to string
  while (value.charAt(0) == " ") {
    value = value.substring(1);
  }
  var sign = 1;
  if (value.charAt(0) == "-") {
    sign = -1;
  }
  return sign * parseDecimal(value);
}

/**
 * This method parses currency into a decimal value.
 *
 * @param value The value to be parsed.
 * @return The positive decimal value of the string, or
 *         <code>NaN</code> if the string contains no
 *         numbers.
 */
function parseCurrency(value) {
  return parseDecimal(value);
}

/**
 * As <code>parseCurrency()</code>, but allows negative
 * values as well. This is not the default because for
 * most applications, allowing negative values can lead
 * to unexpected results and possibly security violations.
 *
 * @param value The value to be parsed.
 * @return The signed decimal value of the string, or
 *         <code>NaN</code> if the string contains no
 *         numbers.
 */
function parseSignedCurrency(value) {
   return parseSignedDecimal(value);
}

/**
 * This method parses a string into a date value.
 * It is less forgiving than Javascript's standard
 * <code>Date.parse()</code> method because it does not
 * allow illegal date combinations like "2/31/1999".
 * It also has better Y2K compliance, converting 2 digit
 * years to a date within 70 years in the past of the
 * current date, or 30 years in the future.
 *
 * @param value The value to be parsed. If the value is
 *              already a <code>Date()</code> object,
 *              this method returns that object.
 * @return The date as a Javascript <code>Date</code>,
 *         or <code>NaN</code> if the date is invalid.
 */
function parseDate(value) {
  if ((typeof value == "object") && (value.constructor == Date)) {
    return value;
  }
  value += ""; // Force conversion to string
  var date = NaN;
  var dateArray = value.split("/");
  if (dateArray.length != 3) {
    dateArray = value.split("-");
  }
  if (dateArray.length == 3) {
    var month = parseInt(dateArray[0]);
    var day = parseInt(dateArray[1]);
    var year = parseInt(dateArray[2]);

    // Rolling Y2K compliance:
    if (year < 100) {
      // Add current century to any 2-digit year
      var today = new Date();
      var todayYear = today.getFullYear();
      var century = parseInt(todayYear / 100);
      year = 100 * century + year;

      // If more than 30 years in future, subtract a century
      if ((year - todayYear) > 30) {
        year = year - 100;
      }

      // If more than 70 years in the past, add a century
      if ((todayYear - year) > 70) {
        year = year + 100;
      }
    }

    if (!(isNaN(day) || isNaN(month) || isNaN(year))) {
      date = new Date(year, month-1, day);

      // Verify that day, month and year are the same
      // as their original values:
      var newMonth = date.getMonth()+1;
      var newDay = date.getDate();
      var newYear = date.getFullYear();
      if ( (newYear != year) || (newMonth != month) ||
           (newDay != day) ) {
        date = NaN;
      }
    }
  }
  return date;
}

/**
 * This method strips the value of any non-numeric
 * characters. It is useful for the preliminary
 * formatting of such fields as Social Security
 * Numbers, Postal Codes and Phone Numbers.
 *
 * @param value The value to be stripped.
 * @return The string with all non-numeric characters
 *         stripped.
 */
function stripNonNumeric(value) {
  var result = "";
  var c;              // The current character.

  value += ""; // Force conversion to string

  for (var i=0; i<value.length; i++) {
    c = value.charAt(i);
    if ((c >= "0") && (c <= "9")) {
      result += c;
    }
  }
  return result;
}

/**
 * This method strips the value of any non-decimal
 * characters. In particular, it retains only "." and
 * the values "0" to "9". It is a useful preliminary
 * for parsing of integer and decimal values.
 *
 * @param value The value to be stripped.
 * @return The string with all non-decimal characters
 *         stripped.
 */
function stripNonDecimal(value) {
  var result = "";
  var c;              // The current character.

  value += ""; // Force conversion to string

  for (var i=0; i<value.length; i++) {
    c = value.charAt(i);
    if (((c >= "0") && (c <= "9")) || (c == ".")) {
      result += c;
    }
  }
  return result;
}



/***********************************************************
*              Section: Get/SetValue Methods               *
*                                                          *
* These methods get and set form field values. There is a  *
* different method for each type of form element. Each     *
* field in the form is assigned <code>getValue()</code>    *
* and <code>setValue()</code> methods appropriate to its   *
* type during the form's initialization (see the           *
* <code>initFormValidation()</code> method for additional  *
* details). These methods provide an object-oriented       *
* wrapper for manipulating field values in a consistent    *
* way. In particular, the following methods work as        *
* expected regardless of the field's actual type:          *
*                                                          *
* <pre>  var value = document.<i>form.field</i>.getValue();*
* document.<i>form.field</i>.setValue(value);</pre>        *
***********************************************************/

/**
 * A <code>getValue()</code> method for the various
 * text form elements (text, password, file and hidden
 * fields, as well as text areas).
 *
 * @return The field value.
 */
function text_getValue() {
  return this.value;
}

/**
 * A <code>setValue()</code> method for the various
 * text form elements (text, password, file and hidden
 * fields, as well as text areas).
 *
 * @param value The value to which the field is set.
 */
function text_setValue(value) {
  this.value = value;
}

/**
 * A <code>getValue()</code> method for select lists.
 *
 * @return The value of the currently selected option,
 *         or an empty string if none is selected.
 */
function select_getValue() {
  var result = "";

  var index = this.selectedIndex;
  if (index >= 0) {
    result = this.options[index].value;
  }

  return result;
}

/**
 * A <code>getText()</code> method for select lists.
 *
 * @return The text of the currently selected option,
 *         or an empty string if none is selected.
 */
function select_getText() {
  var result = "";

  var index = this.selectedIndex;
  if (index >= 0) {
    result = this.options[index].text;
  }

  return result;
}

/**
 * A <code>setValue()</code> method for select lists.
 * The option whose value matches the parameter is
 * selected. If there is no matching value, the list
 * is set so that no option is selected.
 *
 * @param value The value to which the field is set.
 */
function select_setValue(value) {
  var index = -1;

  for (var i=0; i<this.options.length; i++) {
    if (this.options[i].value == value) {
      index = i;
      break;
    }
  }

  this.selectedIndex = index;
}

/**
 * A <code>getValue()</code> method for checkboxes.
 *
 * @return The value of the checkbox if selected,
 *         or an empty string if not selected.
 */
function checkbox_getValue() {
  var result = "";

  if (this.checked == true) {
    result = this.value;
  }

  return result;
}

/**
 * A <code>setValue()</code> method for checkboxes.
 * Selects the checkbox if the value parameter is
 * equivalent to <code>true</code>, and deselects
 * the checkbox is the parameter is equivalent to
 * <code>false</code> (e.g. <code>null</code>, 0
 * or an empty string).
 *
 * @param value  Whether to select this checkbox.
 */
function checkbox_setValue(value) {
  if (value) {
    this.checked = true;
  } else {
    this.checked = false;
  }
}

/**
 * A <code>getValue()</code> method for radio button
 * groups.
 *
 * @return The value of the currently selected button,
 *         or an empty string if none is selected.
 */
function group_getValue() {
  var result = "";

  for (var i=0; i<this.length; i++) {
    if (this[i].checked == true) {
      result = this[i].value;
    }
  }

  return result;
}

/**
 * A <code>setValue()</code> method for radio button
 * groups. The button whose value matches the parameter
 * is selected. If there is no matching value, no button
 * is selected.
 *
 * @param value  The value to which the field is set.
 */
function group_setValue(value) {
  for (var i=0; i<this.length; i++) {
    if (this[i].value == value) {
      this[i].checked = true;
    } else {
      this[i].checked = false;
    }
  }
}

/**
 * A <code>getValue()</code> method for individual radio
 * buttons. It calls the <code>getValue()</code> method
 * of its group.
 *
 * @return The value of the currently selected button,
 *         or an empty string if none is selected.
 */
function radio_getValue() {
  return this.form[this.name].getValue();
}

/**
 * A <code>setValue()</code> method for individual radio
 * buttons. It calls the <code>setValue()</code> method
 * of its group.
 *
 * @param value  The value to which the field is set.
 */
function radio_setValue(value) {
  this.form[this.name].setValue(value);
}



/***********************************************************
*               Section: Validation Functions              *
*                                                          *
* These functions are for validating fields. They are      *
* initialized when the page is loaded, and will be         *
* called automatically when the field value changes or     *
* during form submission.                                  *
***********************************************************/

/**
 * Checks whether a given field is (a) required and
 * (b) missing. If so, it returns an error message.
 * A field is marked required as follows:
 *
 * <pre>
 * validations.<i>field</i>.required = true;</pre>
 * 
 * @param field The field being validated.
 * @return An error string if a required field is
 *         missing and an empty string otherwise. The
 *         error string is in a form suitable for
 *         inclusion in a list of missing fields.
 */
function checkRequired(field) {
  var error = ""; // Error message to display to the user.

  if ( (field.required == true) && 
       (field.getValue() == "") ) {
    error += "    * " + field.label;
    var index = nonRadioIndex(field) + 1;
    if (index > 0) {
      error += ITEM_MSG.replace("{0}", index);
    }
    error += "\n";
  }

  return error;
}

/**
 * Checks whether a given field is within the specified
 * range for that field. If no range is specified, the
 * field always passes this test. The range for a field
 * is set as follows:
 *
 * <pre>
 * validations.<i>field</i>.min = #;
 * validations.<i>field</i>.max = #;</pre>
 *
 * Either the max or the min may be omitted if there is
 * no upper and lower bound. The max and min values are
 * included in the allowed range.
 *
 * @param field The field being validated.
 * @return An error string if a field is out of its
 *         allowed range.
 */
function checkRange(field) {
  var value = parseSignedDecimal(field.getValue());
  // If not a number, return no error:
  if (isNaN(value)) return "";

  var error = ""; // Error message to display to the user.
  var label = field.label;
  var max = field.max;
  var min = field.min;

  if ((min != null) && (max != null)) {
    // Both max and min:
    if ((value < min) || (value > max)) {
      error = RANGE1_ERROR.replace("{0}", label);
      error = error.replace("{1}", field.formatValue(min));
      error = error.replace("{2}", field.formatValue(max));
    }
  } else if (min != null) {
    // If there is a min only:
    if ((value < min)) {
      error = RANGE2_ERROR.replace("{0}", label);
      error = error.replace("{1}", field.formatValue(min));
    }
  } else if (max != null) {
    // If there is a max only:
    if ((value > max)) {
      error = RANGE3_ERROR.replace("{0}", label);
      error = error.replace("{1}", field.formatValue(max));
    }
  }

  return error;
}

/**
 * Checks whether the length of a given field is within
 * the specified range for that field. If no range is
 * specified, the field always passes this test. The range
 * for a field's length is set as follows:
 *
 * <pre>
 * validations.<i>field</i>.minlength = #;
 * validations.<i>field</i>.maxlength = #;</pre>
 *
 * Either the max or the min may be omitted if there is
 * no upper and lower bound. The max and min lengths are
 * included in the allowed range. It may seem that the
 * <code>maxlength</code> property duplicates the
 * <code>MAXLENGTH</code> attribute of HTML text fields.
 * It is not completely redundant, because:
 *
 * <ul>
 *   <li>Text areas do not have a <code>MAXLENGTH</code> 
 *       attribute.</li>
 *   <li>Bugs sometimes allow the user to circumvent
 *       the <code>MAXLENGTH</code> attribute.</li>
 * </ul>
 *
 * For example, in most browsers a text field's
 * <code>MAXLENGTH</code> attribute is checked if the
 * user types the entered characters, but not if the
 * user pastes the characters into the field.
 *
 * @param field The field being validated.
 * @return An error string if a field text's length is
 *         out of its allowed range.
 */
function checkLength(field) {
  var value = field.getValue();
  if (value == null) return "";
  var length = value.length;
  var error = ""; // Error message to display to the user.
  var label = field.label;
  var minlength = field.minlength;
  var maxlength = field.maxlength;

  if ((minlength != null) && (maxlength != null)) {
    // Both max and min:
    if ((length < minlength) || (length > maxlength)) {
      if (minlength == maxlength) {
        error = LENGTH0_ERROR.replace("{0}", label);
        error = error.replace("{1}", maxlength);
      } else {
        error = LENGTH1_ERROR.replace("{0}", label);
        error = error.replace("{1}", minlength);
        error = error.replace("{2}", maxlength);
      }
    }
  } else if (minlength != null) {
    // If there is a min only:
    if ((length < minlength)) {
      error = LENGTH2_ERROR.replace("{0}", label);
      error = error.replace("{1}", minlength);
    }
  } else if (maxlength != null) {
    // If there is a max only:
    if ((length > maxlength)) {
      error = LENGTH3_ERROR.replace("{0}", label);
      error = error.replace("{1}", maxlength);
    }
  }

  return error;
}

/**
 * A method for formatting a form field using the 
 * <code>format</code> method in the field's associated
 * <code>Validator</code> object.
 *
 * @return An empty string, or an error string if
 *         formatting failed. Unlike the underlying
 *         <code>format<i>Type</i></code> method, the
 *         error string is not preceded by "ERROR:"
 *         and is suitable for display to the user.
 */
function field_format() {
  var value = validations[this.name].format(this.getValue());
  value += ""; // Force conversion to string
  if (value.indexOf("ERROR:") == 0) {
    return value.substring(6);
  } else {
    this.setValue(value);
    return "";
  }
}

/**
 * A method for parsing a form field using the
 * <code>parse</code> method in the field's associated
 * <code>Validator</code> object.
 *
 * @return The parse value of the field, or <code>NaN</code>
 *         if the field value does not parse correctly.
 *         The "error value" of <code>NaN</code> is true
 *         for all data types, including dates.
 */
function field_parse() {
  return validations[this.name].parse(this.getValue());
}

/**
 * A standard validation method for fields. This method is
 * assigned to all form elements during form initialization.
 * It is called each time the field value changes: the 
 * "<code>onchange</code>" event for text fields, the 
 * "<code>onblur</code>" event select lists, and the 
 * "<code>onclick</code>" event for checkboxes and radio
 * buttons. This function calls the following standard
 * validating functions:
 *
 * <ul>
 *   <li><code>format()</code> to see if the field
 *       contents are formatted properly.</li>
 *   <li><code>checkRange()</code> to see if the
 *       field value is in its allowed range.</li>
 *   <li><code>checkLength()</code> to see if the
 *       field's text is not too long.</li>
 *   <li><code>compute()</code> to perform any necessary
 *       computations and multi-field validations.</li>
 * </ul>
 *
 * If any errors are returned by these methods, the 
 * errors are displayed to the user in an alert and
 * the form's focus returns to this element.
 */
function field_validate() {
  var error = ""; // The error message.

  // Do not validate non-labeled fields.
  if (!this.label) return;

  // Validate:
  if (this.format) error += this.format();
  error += checkRange(this);
  error += checkLength(this);
  if (this.compute) error += this.compute();

  // Show errors:
  if (error != "") {
    alert(error);
    this.focus();
  }
}

/**
 * A standard validation method for forms. This method
 * is assigned to the form during its initialization.
 * It is called when the form is submitted. This function
 * calls iterates through all the form's fields using a
 * <code>FormIterator</code>. For each field, it calls
 * the following standard validating functions:
 *
 * <ul>
 *   <li><code>checkRequired()</code> to see if a
 *       required value is missing.</li>
 *   <li><code>format()</code> to see if the field
 *       contents are formatted properly.</li>
 *   <li><code>checkRange()</code> to see if the
 *       field value is in its allowed range.</li>
 *   <li><code>checkLength()</code> to see if the
 *       field's text is not too long.</li>
 *   <li><code>compute()</code> to perform any necessary
 *       computations and multi-field validations. This
 *       method is only called if there is no form-level
 *       <code>compute()</code> method.</li>
 * </ul>
 *
 * This method also calls the following form method:
 *
 * <ul>
 *   <li><code>compute()</code> to perform any necessary
 *       computations and multi-field validations for
 *       the entire form. If this method is not present,
 *       each field's <code>compute()</code> method is
 *       called instead.</li>
 * </ul>
 *
 * If any errors are returned by these methods, the 
 * errors are displayed to the user in an alert, and the
 * form submission is canceled.
 *
 * @return <code>true</code> if the field is valid,
 *         <code>false</code> otherwise.
 */
function form_validate() {
  // Prevent form resubmission once submitted:
  if (FORM_IS_SUBMITTED) return false;

  var error = "";         // Error string.
  var missingFields = ""; // For required fields.
  var checkFieldComputations = false;
  
  i = this.getIterator();
  
  if (this.compute) {
    error += this.compute();
  } else {
    checkFieldComputations = true;
  }
  
  while (i.hasNext()) {
    var field = i.next();
    
    missingFields += checkRequired(field);
    if (field.format) error += field.format();
    error += checkRange(field);
    error += checkLength(field);
    if (checkFieldComputations && field.compute) {
      error += field.compute();
    }
  }

  // Prepend missing fields, if any:
  if (missingFields != "") {
    error = REQUIRED_ERROR.replace("{0}", missingFields) +
            error;
  }

  if (error == "") { // If there were no errors:
    FORM_IS_SUBMITTED = true;
    return true;     //   Submit the form.
  } else {           // Otherwise:
    alert(error);    //   Alert the user of errors.
    return false;    //   Cancel the form submission.
  }
}



/***********************************************************
*         Section: Form Initialization Functions           *
*                                                          *
* These functions are used to initialize all the other     *
* validation functions in this script.                     *
***********************************************************/

/**
 * A constructor for creating an iterator for walking
 * through form elements with validations. This iterator
 * has two methods:
 *
 * <ul>
 *   <li><code>hasNext()</code>: Returns <code>true</code>
 *       if the iterator has not yet reached the final
 *       form element.</li>
 *   <li><code>next()</code>: Returns the next element
 *       with validations and increments the iterator to
 *       the element following it.</li>
 * </ul>
 *
 * The iterator is used as follows:
 *
 * <pre>
 * i = form.getIterator();
 * while (<b>i.hasNext()</b>) {
 *   var field = <b>i.next()</b>;
 *   <i>// Work with each field</i>
 * }</pre>
 *
 * @param form The form to iterate through.
 */
function FormIterator(form) {
  this.form = form;
  this.count = 0;

  // Set the hasNext() function:
  this.hasNext = function() { 
    return(this.count < this.form.length);
  }

  // Set the next() function
  this.next = function() {
    var field = this.form[this.count];
    do {
      this.count++;
    } while ((this.count<this.form.length) && 
             (!(this.form[this.count].label) || 
              isNotFirstRadioButton(this.form[this.count])));
    return field;
  }

  // Move forward to the first element in the iteration:
  if (!(this.form[this.count].label)) {
    this.next();
  }
}

/**
 * A function that moves the cursor from the given
 * element to the next editable (not readonly) field.
 * It is assigned to the <code>onfocus</code> event
 * handler for read-only fields.
 *
 * @parameter field The field with the current focus.
 */
function goToNextEditableField(field) {
  var index = -1;
  var elements = field.form.elements;
  for (i=indexInForm(field); i<elements.length; i++) {
    if (!elements[i].readonly) {
      index = i;
      break;
    }
  }
  if (index > 0) {
    elements[index].focus();
  } else {
    field.blur();
  }
}

/**
 * This function assigns form validiation properties and
 * methods to a field during form initialization. In
 * particular, it copies the validation properties and
 * methods assigned to the matching <code>Validator</code>
 * in the <code>validations</code> collection to the field.
 *
 * <pre>
 * // Example validations for an amount field:
 * validations.amount = new Validator("Item Amount");
 * validations.amount.datatype = "Integer";
 * validations.amount.compute = computeTotals;
 *
 * ...
 *
 * // The validations copied to the amount field:
 * document.<i>form</i>.amount.label = "Item Amount";
 * document.<i>form</i>.amount.datatype = "Integer";
 * document.<i>form</i>.amount.compute = computeTotals;</pre>
 *
 * In addition, the field will be assigned getter, setter
 * and validate methods appropriate to the field's type.
 * The assignments are:
 *
 * <ul>
 *   <li><code>getValue()</code>: Returns the field's
 *       value.</li>
 *   <li><code>setValue()</code>: Sets the field's
 *       value.</li>
 *   <li><code>validate()</code>: Performs basic
 *       form validations.</li>
 *   <li><code>onchange/onblur/onclick</code> event: Calls
 *       the <code>validate()</code> method.</li>
 * </ul>
 *
 * Finally, the following methods are called to format the
 * field and perform multi-field computations, if these
 * methods are present:
 *
 * <ul>
 *   <li><code>format()</code></li>
 *   <li><code>compute()</code></li>
 * </ul>
 *
 * Note that format functions will be assigned automatically
 * for fields with known datatypes. Any validation errors that
 * occur during initialization are suppressed.
 *
 * @param field  The field to be initialized.
 */
function initFieldValidation(field) {
  var name = field.name;

  // Process validations for field:
  if (validations[name]) {
    var validationsItem = validations[name];

    // Set parse and format functions from data type:
    if (validationsItem.datatype) {
      var formatFunction = "format" + validationsItem.datatype;
      var parseFunction = "parse" + validationsItem.datatype;
      if (window[formatFunction]) {
        validationsItem.format = window[formatFunction];
      }
      if (window[parseFunction]) {
        validationsItem.parse = window[parseFunction];
      }
    }

    // Transfer matching validations properties to field:
    for (property in validationsItem) {
      if (property == "parse") {
        field.parse = field_parse;
        field.parseValue = validationsItem.parse;
      } else if (property == "format") {
        field.format = field_format;
        field.formatValue = validationsItem.format;
      } else {
        field[property] = validationsItem[property];
      }
    }
  }

  // Set field validation and refocusing methods:
  field.validate = field_validate;
  if (field.readonly) {
    field.onfocus = function() { goToNextEditableField(this); }
  }

  // Assign getter, setter and event handlers for the field:
  if (field.type=="checkbox") {
    field.getValue = checkbox_getValue;
    field.setValue = checkbox_setValue;
    field.onclick = function() { this.validate(); }
  } else if (field.type=="radio") {
    field.getValue = radio_getValue;
    field.setValue = radio_setValue;
    field.onclick = function() { this.validate(); }

    // Transfer matching validations properties to radio group:
    if (isFirstRadioButton(field)) {
      var group = field.form[field.name];
      group.getValue = group_getValue;
      group.setValue = group_setValue;
      if (validations[name]) {
        var validationsItem = validations[name];
        for (property in validationsItem) {
          if (property == "parse") {
            group.parse = function() { return this[0].parse(); }
          } else if (property == "format") {
            group.format = function() { return this[0].format(); }
          } else {
            group[property] = validationsItem[property];
          }
        }
      }
    }
  } else if (field.type=="select-one") {
    field.getValue = select_getValue;
    field.setValue = select_setValue;
    field.getText = select_getText;
    field.changed = false;

    // A combination of "onchange" and "onblur" is used
    // to trigger validation for select lists so that
    // validation only occurs after (a) the field value
    // has changed and (b) the user leaves the field.
    field.onchange = function() { this.changed = true; }
    field.onblur = function() { 
      if (this.changed) {
        this.changed = false;
        this.validate();
      }
    }
  } else if ( (field.type=="text") ||
              (field.type=="hidden") ||
              (field.type=="textarea") ||
              (field.type=="file") ||
              (field.type=="password") ) {
    field.getValue = text_getValue;
    field.setValue = text_setValue;
    field.onchange = function() {this.validate();}
  }
}



/***********************************************************
*              Section: Field Index Functions              *
*                                                          *
* These methods return information about the index of a    *
* field element within its form or the group of elements   *
* with the same name.                                      *
***********************************************************/

/**
 * A function for determining an element's index within
 * its form.
 *
 * @param field  The field being checked.
 * @return The index of the element within its form.
 */
function indexInForm(field) {
  var elements = field.form.elements;
  var index = -1;
  for (i=0; i<elements.length; i++) {
    if (elements[i] == field) {
      index = i;
      break;
    }
  }
  return index;
}

/**
 * A function for determining an element's index within
 * its group of elements with the same name.
 *
 * @param field  The field being checked.
 * @return The index of the element within its group, or
 *         -1 if the element is not in a group.
 */
function indexInGroup(field) {
  var index = -1;
  var group = field.form[field.name];
  if ((typeof group == "object") && (group.length)) {
    for (var i=0; i<group.length; i++) {
      if (group[i] == field) {
        index = i;
        break;
      }
    }
  }
  return index;
}

/**
 * A function for determining a non-radio button's index
 * within its group of elements with the same name.
 *
 * @param field  The field being checked.
 * @return The index of a non-radio button within its
 *         group, or -1 if (a) the element is not in
 *         a group or (b) the field is a radio button.
 */
function nonRadioIndex(field) {
  if (field.type != "radio") {
    return indexInGroup(field);
  } else {
    return -1;
  }
}

/**
 * A function for determining a radio button's index
 * within its radio button group.
 *
 * @param field  The field being checked.
 * @return The index of a radio button within its
 *         group, or -1 if the field is not a radio
 *         button.
 */
function radioIndex(field) {
  if (field.type == "radio") {
    return indexInGroup(field);
  } else {
    return -1;
  }
}

/**
 * A function for determining the first radio button
 * in a group.
 *
 * @param field  The field being checked.
 * @return <code>true</code> if this is the first radio
 *         button in a group of radio buttons. This
 *         means it returns <code>false</code> if the
 *         field is not a radio button at all.
 */
function isFirstRadioButton(field) {
  return(radioIndex(field)==0);
}

/**
 * A function for determining if the field is a radio
 * button but not the first button in its group.
 *
 * @param field  The field being checked.
 * @return <code>true</code> if the field is a radio
 *         button, but not the first radio button of
 *         its group. This value is not necessarily
 *         the same as
 *         <code>!isFirstRadioButton(field)</code>.
 */
function isNotFirstRadioButton(field) {
  return(radioIndex(field)>0);
}



//-----------------------------------------------------
// Fundamental object creation:
//-----------------------------------------------------

validations = new Object();
formdata = new Object();
formitems = new Array();

document.onload = function() { initFormValidation(); }
FORM_IS_SUBMITTED = false;



//-----------------------------------------------------
// Error string definitions:
//-----------------------------------------------------

// {0} is the field label:
NUMBER_ERROR = "{0} is not a number.\n\n";

// {0} is the field label:
PHONE_ERROR = "{0} is not a valid phone number (###-###-#### including area code).\n\n";

// {0} is the field label:
SSN_ERROR = "{0} is not a valid Social Security Number (###-##-####).\n\n";

// {0} is the field label:
POSTAL_ERROR = "{0} is not a valid US Zip Code.\n\n";

// {0} is the field label:
NO_SPACE_ERROR = "{0} may not have spaces.\n\n";
EMAIL_ERROR = "{0} is not a valid email address (for example: name@domain.com).\n\n";

// {0} is the field label:
DATE_ERROR = "{0} is not a valid date.\n\n";

// {0} is the item index; the leading space is necessary
ITEM_MSG = " (item {0})";

// {0} is the label, {1} the min and {2} the max
RANGE1_ERROR = "The value for {0} must be between {1} and {2}.\n\n";

// {0} is the label, {1} the min
RANGE2_ERROR = "The value for {0} cannot be less than {1}.\n\n";

// {0} is the label, {1} the max
RANGE3_ERROR = "The value for {0} cannot be more than {1}.\n\n";

// {0} is the label, {1} the maxlength:
LENGTH0_ERROR = "{0} must be exactly {1} characters.\n\n";

// {0} is the label, {1} the minlength and {2} the maxlength:
LENGTH1_ERROR = "{0} must be between {1} and {2} characters.\n\n";

// {0} is the label, {1} the minlength:
LENGTH2_ERROR = "{0} cannot be less than {1} characters.\n\n";

// {0} is the label, {1} the maxlength:
LENGTH3_ERROR = "{0} cannot be more than {1} characters.\n\n";

// {0} is the missingFields list:
REQUIRED_ERROR = "The following required fields are missing:\n{0}\n";
